{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "bc96dc43-8bbf-4d82-9010-34b802ae4798",
   "metadata": {},
   "source": [
    "# Coversational Interface - Chatbot with Claude LLM\n",
    "In this notebook, we will build a chatbot using the Foundation Models (FMs) in Amazon Bedrock. For our use-case we use Amazon Nova Lite as our FM for building the chatbot and InMemoryChatMessageHistory to store the conversation history.\n",
    "\n",
    "## Overview\n",
    "\n",
    "Conversational interfaces such as chatbots and virtual assistants can be used to enhance the user experience for your customers. Chatbots uses natural language processing (NLP) and machine learning algorithms to understand and respond to user queries. Chatbots can be used in a variety of applications, such as customer service, sales, and e-commerce, to provide quick and efficient responses to users. They can be accessed through various channels such as websites, social media platforms, and messaging apps.\n",
    "\n",
    "![Using Amazon Bedrock to support a multi-turn conversation with AI](.././images/chatbot_bedrock.png)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3ac42792-dbd3-40a1-ab0d-50cdaca25fbe",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install --upgrade -q -r requirements.txt"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c9437c62-8c7f-41b4-8b3e-8af03d70441a",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Restart kernel\n",
    "from IPython.core.display import HTML\n",
    "HTML(\"<script>Jupyter.notebook.kernel.restart()</script>\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac9e2e43-c77c-4c8e-a581-f43bfd89fe86",
   "metadata": {},
   "outputs": [],
   "source": [
    "import boto3\n",
    "import botocore"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e892226c-1117-461a-b72a-a5616c7a45ea",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_aws.chat_models import ChatBedrock\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "from langchain_core.chat_history import InMemoryChatMessageHistory"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "80151c5c-d919-46d2-83d3-c52d0c550bf5",
   "metadata": {},
   "source": [
    "### Set up"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b0761cfa-a2c2-4d52-be5a-dddae776a9f9",
   "metadata": {},
   "outputs": [],
   "source": [
    "boto3_session = boto3.session.Session()\n",
    "region = boto3_session.region_name\n",
    "\n",
    "# the statement below can be used to override the region in the session\n",
    "#region = \"us-west-2\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fc1b7041-c816-4764-8672-4b7057e7e705",
   "metadata": {},
   "source": [
    "### Run the chatbot"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "32bc34ff-0ee1-4fd2-abff-d00edc0586b0",
   "metadata": {},
   "source": [
    "Set up parameters for the model and create a client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8fb59de4-fcdf-4cfd-b243-2f16da715516",
   "metadata": {},
   "outputs": [],
   "source": [
    "model = \"us.amazon.nova-lite-v1:0\"\n",
    "temperature = 0.1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "28852f4f-d7e1-49a8-959b-1a8eb8e8083d",
   "metadata": {},
   "outputs": [],
   "source": [
    "llm_chat = ChatBedrock(\n",
    "    model_id=model, \n",
    "    model_kwargs={\"temperature\": temperature},\n",
    "    region_name=region\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e94cdcab-ce24-4fb0-ba5f-cd4359690fdc",
   "metadata": {},
   "source": [
    "Passing conversation state into and out a chain is vital when building a chatbot. The [RunnableWithMessageHistory class](https://python.langchain.com/api_reference/core/runnables/langchain_core.runnables.history.RunnableWithMessageHistory.html) lets us add message history to certain types of chains. It wraps another Runnable and manages the chat message history for it. Specifically, it loads previous messages in the conversation BEFORE passing it to the Runnable, and it saves the generated response as a message AFTER calling the runnable. This class also enables multiple conversations by saving each conversation with a session_id - it then expects a session_id to be passed in the config when calling the runnable, and uses that to look up the relevant conversation history. You have to implement a function like the get_session_history() which will take as input the session-id and return the conversation for the session. Here we have a simple implementation of the conversation history, using the InMemoryChatMessageHistory class"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "18901bf2-b71f-4941-9a39-f53d05d0ae38",
   "metadata": {},
   "outputs": [],
   "source": [
    "store = {}\n",
    "\n",
    "def get_session_history(session_id):\n",
    "    if session_id not in store:\n",
    "        store[session_id] = InMemoryChatMessageHistory()\n",
    "    return store[session_id]\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(llm_chat, \n",
    "                                                  get_session_history)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4bb8438-b0b0-42a3-aacd-53d1fa316d8d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"hi - i am bob!\")],\n",
    "    config={\"configurable\": {\"session_id\": \"1\"}},\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "60275cab-a8d8-470a-bfa3-8b78636d28d7",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"whats my name?\")],\n",
    "    config={\"configurable\": {\"session_id\": \"1\"}},\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "4c975f46-0e2a-4823-bdd9-be2a8ec9ce9d",
   "metadata": {},
   "source": [
    "At this point the store has 1 key (session_id = '1') and its value is a list with 4 messages:\n",
    " [HumanMessage, AIMessage, HumanMessage, AIMessage]\n",
    "\n",
    "All langchain messages have 3 properties: a role, content, response_metadata. The HumanMessage returns 1 property, the *content* (e.g. the message that the user passed) whereas the AIMessage also returns non-empty *response_metadata*. It also includes the property *usage_metadata*, a dictionary with these keys: input_tokens, output_tokens, total_tokens (these are also included in the response_metadata) \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8f45c386-dca0-4b9d-98fd-4df46e26b52e",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(store)\n",
    "# uncomment the following line to take a closer look at the message associated with session_id='1'\n",
    "#store['1'].messages[1]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6bc8ebfd-92bc-4ac7-b537-9510a8b0f2bc",
   "metadata": {},
   "source": [
    "### Create a Multi-Lingual Greeter Chatbot! \n",
    "Building on our above example the RunnableWithMessageHistory class from langchain which serves to:\n",
    "1) retain `InMemoryChatMessageHistory` for each follow up message within the same session ID\n",
    "2) call the LLM ([*Runnable*](https://python.langchain.com/v0.1/docs/expression_language/interface/))\n",
    "3) write the AI response message back into the `InMemoryChatMessageHistory`\n",
    "\n",
    "Now we will introduce [ChatPromptTemplate](https://python.langchain.com/api_reference/core/prompts/langchain_core.prompts.chat.ChatPromptTemplate.html) to configure our chat application to be multi-lingual!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e5151501-1ec6-4400-9cf8-82194d0836e4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\",\"You're an assistant who speaks in {language}. Translate the user input\"),\n",
    "    MessagesPlaceholder(variable_name=\"history\"),\n",
    "    (\"human\", \"{question}\"),\n",
    "])\n",
    "\n",
    "chain = prompt | llm_chat\n",
    "\n",
    "chain_with_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"question\",\n",
    "    history_messages_key=\"history\",\n",
    ")\n",
    "\n",
    "lang = \"French\"\n",
    "print(chain_with_history.invoke(\n",
    "    {\"language\": lang, \"question\": \"Hi my name is John\"},\n",
    "    config={\"configurable\": {\"session_id\": \"2\"}}\n",
    "))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5c2288d1-4dc8-4de2-8e3a-a421bf40a1d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(chain_with_history.invoke(\n",
    "    {\"language\": lang, \"question\": \"What is my name?\"},\n",
    "    config={\"configurable\": {\"session_id\": \"2\"}}\n",
    "))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "21f8bb4e-119e-4aea-a541-260321cabc8b",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(store['2'].messages)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
